package com.forg.mvc.core;

import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.forg.mvc.core.annotation.Controller;
import com.forg.mvc.core.annotation.InterceptPath;
import com.forg.mvc.core.annotation.RequestMapping;
import com.forg.mvc.core.common.Constant;
import com.forg.mvc.core.common.RequestUtil;
import com.forg.mvc.core.container.CtrlCntr;
import com.forg.mvc.core.container.InterceptorCntr;
import com.forg.mvc.core.handle.InterceptorHandler;
import com.forg.mvc.core.handle.RequestHandler;

public class RequestDispatcher {
    
    private Logger logger = LoggerFactory.getLogger(RequestDispatcher.class);
    
    private static RequestDispatcher r = null;
    
    private Map<RequestID,RequestHandler> requestMapping = new HashMap<>();
    
    
    private Map<String,List<InterceptorHandler>> interceptMapping = new HashMap<>();
    private RequestDispatcher(){
    }
    
    public static synchronized RequestDispatcher getInstance(){
        
        if(r == null){
            r = new RequestDispatcher();
        }
        
        return r;
    }

    public synchronized void init(){
        initInterceptorMapping();
        initRequestMapping();
    }
    
    private void initInterceptorMapping(){
        
        long startTime = System.currentTimeMillis();
        
        InterceptorCntr.init();
        
        List<Class<?>> intcptClassList = InterceptorCntr.getInterceptorClassList();
        
        for(Class<?> intcptClzz  : intcptClassList){
            
            Method[] intcptMethods = intcptClzz.getDeclaredMethods();
            
            for(Method m : intcptMethods){
                if(m.isAnnotationPresent(InterceptPath.class)){
                    
                    String path = m.getAnnotation(InterceptPath.class).value();
                    
                    if(StringUtils.isEmpty(path)){
                        continue;
                    }
                    
                    int priority = m.getAnnotation(InterceptPath.class).priority();
                    
                    m.setAccessible(true);
                    
                    List<InterceptorHandler> handlers = interceptMapping.get(path);
                    if(handlers == null){
                        handlers = new ArrayList<>();
                        interceptMapping.put(path,handlers);
                    }
                    
                    Object instance = InterceptorCntr.getInstance(intcptClzz);
                    InterceptorHandler i = new InterceptorHandler(instance,m,priority);
                    
                    handlers.add(i);
                }
            }
        }
        
        if(!interceptMapping.isEmpty()){
            for(Map.Entry<String,List<InterceptorHandler>> e : interceptMapping.entrySet()){
                
                Collections.sort(e.getValue());
            }
        }
        
    }
    
    private void initRequestMapping(){
        
        long startTime = System.currentTimeMillis();
        
        if(!CtrlCntr.init()){
            throw new IllegalStateException("CtrlContainer init error.");
        }
        
        List<Class<?>> ctrlClassList = CtrlCntr.getCtrlClassList();
        
        for(Class<?> ctlClzz : ctrlClassList){
            
            //先获取ctrl
            String ctrlPath = ctlClzz.getAnnotation(Controller.class).path();
            if(ctrlPath.endsWith(Constant.BACKSLASH)){
                ctrlPath = ctrlPath.substring(0, ctrlPath.length()-1);
            }
            
            Method[] actionMethods = ctlClzz.getDeclaredMethods();
            
            for(Method m : actionMethods){
                if(m.isAnnotationPresent(RequestMapping.class)){  
                    
                    m.setAccessible(true);
                    
                    String methodPath = m.getAnnotation(RequestMapping.class).value();
                    if(!methodPath.startsWith(Constant.BACKSLASH)){
                        methodPath = Constant.BACKSLASH + methodPath;
                    }
                    
                    RequestMethodEnum[] method = m.getAnnotation(RequestMapping.class).method();
                    for(RequestMethodEnum md : method){
                        RequestID rid = new RequestID(md,ctrlPath + methodPath) ;
                        
                        RequestHandler rhd = new RequestHandler(CtrlCntr.getInstance(ctlClzz),m );
                        requestMapping.put(rid,rhd);
                    }
                }
            }
        }
        
        logger.debug("RequestDispatcher request mapping init cost {} ms",System.currentTimeMillis() - startTime);
    }
    
    private RequestHandler getRequestHandle(){
        
        RequestWraper requestWraper = RequestContext.getRequest();
        if(requestWraper == null ){
            throw new IllegalStateException("no request find in current thread.");
        }
        
        RequestHandler requestHandle = requestMapping.get(requestWraper.getRequestID());
        
        if(requestHandle == null){
            throw new IllegalStateException("no handle find for request :" + requestWraper);
        }
        
        logger.debug("dispatch request [{}] to [{}]",requestWraper,requestHandle);
        return requestHandle;
    }
    
    public void dispatch(){
        
        try {
            
            //step1. interceptor 
            boolean doRequest = true;
            List<InterceptorHandler> handles = interceptMapping.get(RequestUtil.getPath());
            if(CollectionUtils.isNotEmpty(handles)){
                for (Iterator<InterceptorHandler> iterator = handles.iterator(); iterator.hasNext();) {
                    
                    InterceptorHandler interceptorHandler = (InterceptorHandler) iterator.next();
                    
                    if(!interceptorHandler.handle()){
                        doRequest = false;
                        break;
                    }
                }
            }
            
            //step2. handle request.
            try {
                if(doRequest){
                    getRequestHandle().handle();
                }
            } catch (IllegalStateException e) {
                //no handler for this request.
                PrintWriter writer = RequestContext.getResponse().getResp().getWriter();
                writer.write(Constant.DEFAULT_404 + Constant.HTML_BR + e.getMessage());
                writer.close();
            }
            
        } catch (Exception e) {
            
        }finally {
            RequestContext.clear();
        }
    }

	
}
